using System;
using System.IO;
using System.Collections.Generic;
using WDFactoryLib;

namespace TextMenus
{
    /// <summary>
    /// An interactive I/O (console or file) menu.
    /// Lists various options and accepts customized input
    /// </summary>
	public class TextMenu: FactoryUser
	{
		private static string DEFAULT_HEADER = "MENU";
		private static string DEFAULT_FOOTER = String.Empty;
		private static string DEFAULT_PROMPT = "Enter your selection: ";

		/// <summary>
		/// A string which will be displayed above the menu options whenever
		/// the menu is run
		/// </summary>
		public string header;

		/// <summary>
		/// A string which will be displayed below the menu options whenever
		/// the menu is run
		/// </summary>
		public string footer;

		/// <summary>
		/// A string used to prompt the user to enter his selection
		/// </summary>
		public string prompt;

        /// <summary>
        /// Event which is invoked when the user chooses an option from the menu,
        /// before invoking the delegate bound to the option itself
        /// </summary>
        public event Action<MenuInputInfo> BeforeMenuOptionChosen;

        /// <summary>
        /// Event which is invoked when the user chooses an option from the menu,
        /// after invoking the delegate bound to the option itself
        /// </summary>
        public event Action<MenuInputInfo> AfterMenuOptionChosen;

		private int counter;
		private IFactory factory;
		private IDictionary<int,MenuOption> actionMap;
        private TextReader input;
        private TextWriter output;

		public TextMenu()
		{
			InitializeInstance();
		}

		internal TextMenu (IFactory _factory)
		{
			InitializeInstance();
		}

        private void InitializeInstance()
        {
            BeforeMenuOptionChosen += DoNothing;
            AfterMenuOptionChosen += DoNothing;
            header = DEFAULT_HEADER;
            footer = DEFAULT_FOOTER;
            counter = 0;
            actionMap = factory.GetNewDictionary<int, MenuOption>();
        }

        private void DoNothing(MenuInputInfo i)
        {
        }

        internal IDictionary<int, MenuOption> ActionMap
        {
            get { return actionMap; }
        }

		public void AddMenuOption (MenuOption option)
		{
			actionMap[++counter] = option;
		}

		public void RemoveMenuOption (MenuOption option)
		{
			foreach (KeyValuePair<int,MenuOption> kv in actionMap)
			{
				if (kv.Value == option)
				{
					actionMap.Remove(kv);
					break;
				}
			}
		}

        /// <summary>
        /// Run the menu to obtain a single result, without invoking ANY events or delegates
        /// </summary>
        /// <param name="input"></param>
        /// <param name="output"></param>
		public MenuInputInfo RunOnce (TextReader input, TextWriter output)
		{
			MenuInputInfo inp;

			DisplayMenu (output);
			inp = new MenuInputInfo (this, GetLine (input));

			try {
                inp = new MenuInputInfo(this, GetLine(input));
			} catch (Exception ex) {
				if (ex is FormatException || ex is KeyNotFoundException)
				{
					DisplayInvalidInputMessage();
                    return null;
				}
				else
					throw new Exception("Unexpected Exception Type",ex);
			}

            /*
            BeforeMenuOptionChosen.Invoke(inp);

            inp.SelectedOption.Invoke(inp);

            AfterMenuOptionChosen.Invoke(inp);
            */

            return inp;
		}

		private string GetLine(TextReader input)
		{
			return input.ReadLine();
		}

		private void DisplayInvalidInputMessage()
		{
			output.WriteLine ("Invalid Selection; Try Again");
		}

		private void DisplayMenu(TextWriter output)
		{
			output.WriteLine (header);
			foreach (KeyValuePair<int,MenuOption> kv in actionMap)
				output.WriteLine("{0} - {1}", kv.Key, kv.Value);
			output.WriteLine (footer);
			output.Write(prompt);
		}
	}
}

